Repository navigation
feat(core-ts): entry_refs and reference integrity, gated off (RFC-0045 step 2a, #114) - #205
Merged
Merged
Conversation
…p 2a, #114) Migration 0033 adds entry_refs (in SITE_TABLES). Entry writes canonicalize changed member ids to group ids, AND the target guard and the definitions anchor into their CAS, and rewrite their rows with a plain INSERT that reads `shared` from the site row at batch time (a NULL fails the batch and maps to ConflictError). Deletes, moves and group changes carry the collections anchor and the blocking-rows condition with the planned pairs, and refuse during a conversion into references (field_operation_running). Translation writes carry the written-group and dependants guards (translation_group_in_use); readsOriginal/originalOf require an original's group to be its own id. Definition changes that would strand values are refused (field_key_in_use, removed target collections or sub-fields, multiple). Field operations naming the new types answer field_operation_unsupported. Imports store canonical bodies and rebuild entry_refs beside a written node set; the snapshot gate checks held site bodies of new snapshots; site rows stored verbatim are rewritten canonically (nodes.canonical job, local store open). SiteGraphStoreOptions.fieldTypes and CopySiteOptions.fieldTypes carry the capability; production passes none. Shared suite: testing-references.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Madraka
force-pushed
the
claude/rfc45-store-integrity-2955a9
branch
from
November 11, 2026 08:32
32e0f41 to
d107895
Compare
…ted ORDER BY (PR #205 CI) workerd's SQLite (D1, Durable Objects) does not resolve the outer json_each alias p inside the shared subquery's ORDER BY (no such column: p.value). The exact key still wins over a running rename's fromKey, and no match still yields NULL, which fails the batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…ard (PR #205 review) - A (#5, #3, #6, #7): the definitions and collections anchors read the fragment's stored text when planned (checked against the planned Site, else ConflictError) and compare that text at batch time, so a site row stored verbatim anchors like any other. The canonical-rows job and its local-open run are removed: nothing depends on canonical site rows. - B (#2, #4): in a site with reference fields every entry write carries the definitions anchor (status-only publishes included); the shared subquery only matches reference and repeater definitions, so a retyped key fails the batch. - C (#0): usable members are one json_each count over the needed collection lists (six parameters, per-pair semantics kept). - D (#8, #15): the list-values fingerprint format moves to 2, so rows built under the old readsOriginal rebuild once. - E (#16): a referrer write naming an entry outside the field's collections is unknown_entry (422); reference_target_moved stays the move's 409. - F (#12): a reference sub-field added over a key stored items hold is field_key_in_use, counted and guarded on the site CAS. - G (#1): racing cases for the R3 narrowing in both orders, R13 for a move and a group change, the #2 interleaving, a verbatim site row, 22 blocking pairs, and SQL tests of the shared subquery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
ADR-0046 names field_operation_unsupported, the referrer-publish refusal, the text anchors, the untested R9 and R14 and what moves to 2b; the RFC status line says step 2 is split. The changeset drops the removed job and adds the list-values rebuild. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…cal-rows job (PR #205) §2.1 and §2.2: the anchors read the fragment's stored text when planned, check it against the planned Site and compare it as text at batch time; the definitions anchor is on every entry write in a site with reference fields. §2.4: the canonical-rows job is dropped and step 7 does not run it; canonical import bodies stay as hygiene. Watch list and review log follow, and an "Amended in 2a" note lists the review's design changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC-0045 rollout step 2a ("Store and integrity", first half) for #114. The PM approved splitting step 2 in two.
referenceandrepeateron every path (Studio, MCP, API, import, sync, push, pull, restore). No production caller passes aFieldTypeSupport.SiteGraphStoreOptions.fieldTypesandCopySiteOptions.fieldTypes.This PR first held all of step 2 in ten commits. It was force-pushed to the 2a scope; the full version is kept on a local branch for 2b.
What 2a contains, per §9 step 2 bullet
Migration 0033 and
entry_refsinSITE_TABLES.packages/storage/migrations/0033_entry_refs.ts: the table and theentry_refs_targetindex, DDL only.migrate.test.tslists it.The target guard, the definitions anchor bound as planned, and rows with
shared/root_key.core-ts/src/references/sql.ts: membership, "shown by a published entry", the target guard, andusableMembers.Site; a mismatch is aConflictError. The batch then compares that exact text, with SQLNULLwhen the fragment is absent.references/writer.ts. It turns member ids into group ids in changed values. Before the batch it checksunknown_entry,reference_target_moved, the entry's own group andwhere.fieldsorcollection.DELETE, then a plainINSERT … SELECTthat readssharedfrom the site row at batch time, by key orfromKey, exact key first. The read is an aggregate, which workerd resolves, and only reference or repeater definitions match.NULLsharedfails the batch. OnlyNOT NULL constraint failed: entry_refs.sharedmaps toConflictError.store/entry-integrity.tsplans the links, references and leave rules together.Delete, move, group-change and publish rules, with the collections anchor and the policy.
references/leave.ts: every delete, move and group change carries the collections anchor.NOT EXISTS(blocking) OR (usable members AND NOT EXISTS(blocking with a pair not in P)). A move keeps the pairs whose field allows its new collection.json_eachcount over the needed collection lists. It binds six parameters however many pairs block.entry_referenced(up to 20 referrers and the total) andreference_target_moved.R1 to R16 as shared-suite cases. See the table below.
Chunked
entryRefsRebuildin import, sync and restore (references/rebuild.ts).nodeBatchunder the same condition asrefsRebuild.sharedis computed in JS; keys are read throughfromKey.The membership rule everywhere, the relink refusal, the written-group guard, and the
readsOriginal/originalOftightening.checkTranslationLinksreturns the written-group guard (omitted for the node's own id) and the dependants guard (translation_group_in_use). Pages get them too.fields/resolve.tsandstore/entry-fields.ts.field_operation_running. A marker that converts values into references refuses deletes, moves and group changes in its target collections. The collections anchor fails a batch that was planned before the marker landed.Canonical bodies in
importSite, andCopySiteOptions.fieldTypes.nodeBatchstores canonical JSON.checkBundleand the snapshot gate."Operations naming the new types refused" (half of that bullet).
checkOperationanswersfield_operation_unsupported. Phases A and C check definitions with the store's capability.§2.8 definition changes (
references/definition-changes.tsviastore/site-fields.ts), needed by R3:field_key_in_use, for a new field or a new reference sub-field over keys that items hold;multiple.Each count is guarded on the site CAS.
Refusal codes. 409 or 422 in
api/src/errors.ts, and MCP hints infield-hints.ts.translation_group_in_usecan already happen in production.unknown_entry(422).reference_target_moved(409) is only for moves.List rows after the tightening. The list-values fingerprint format moves to 2, so listed collections rebuild once.
Step-1 review note (held blobs).
prepareImportruns the gate on site bodies that the bundle's new snapshots point at but the target already holds (not for a capable site). The test fails without the fix.Moves to 2b
2b branches from main after this merges. These are exactly the step-2 bullets, or parts of bullets, that are not here:
referenceIssuesincloudhal import --dry-run(the second half of the rebuild bullet), with OQ 14's relinked-original warning.fieldAssetRefs,fieldAssetIssueswith sub-field types and item paths, and the prefix-match skip set (entry-fields.ts:107,field-refs.ts:81).depsOf(core-ts/src/release-deps.ts).forEdition, with emptied items dropped.fields: 2capability in the gate (siteFieldTypeSupport,FIELDS_FEATURE_VERSION,FIELDS_SUPPORTED).entries/pickandentries/:id/references, and their scopes. The entry routes' new status codes are already in 2a.None of these is reachable in production before step 7, because no reference value can exist.
R-cases and where they are tested
The cases are in
packages/core-ts/src/testing-references.ts, which runs on every backend throughdescribeSiteGraphStore. Each one is two interleaved batches viaracingDriver.events.venue[places, halls] againsttalks.room[places]), plus "R13 for a move" and "R13 for a group change"sharedread (exact key overfromKey; a retyped or missing key gives NULL) has SQL tests inreferences-store.test.ts.The suite also covers:
translation_group_in_use;NULLanchor;sharedrule;test/references-store.test.ts(sqlite) covers:fixtures/valid/sitefile, stored canonically and with every key order reversed, including fractional and1e21bounds; a mismatch with the planned site, and a change after planning;sharedsubquery:fromKey, the exact key winning, and a retyped or missing key failing;fromKey;NOT NULLmapping (an unrelated column is not mapped);Deviations
collections. That is the stated invariant, and it does not over-refuse when two fields allow different collections.translationfrom an original whose group stays its own id is not refused. The relink refusal applies when a node's group changes.json(…) IS json(canonical). They compare the fragment's stored text, read when the write is planned and checked against the plannedSite. There is no second read the plan did not see, and key order never matters, so the §2.4 canonical-rows job is not shipped. (PM review, A.)entry_referenced.details.referrerslists the holders of blocking rows, not the translations that inherit from them.wheremiss on a newly chosen target, are refused asinvalid_value(invalid_fields). The RFC names no code for either.whereis checked before the batch only, as the RFC says.set_field_localizedphase A'sUPDATE entry_refs SET sharedis not wired, because operations on reference fields are refused until step 4.sharedread is a second line of defence. R16 simulates phase A with a raw write of the site row.field_key_in_useare added, including a reference sub-field added over keys that items hold. They are not in the literal bullet list, but R3 needs them and the step-1 hand-over assigned them to step 2.Tests run (locally, one file at a time)
Run on cb0ad73:
test/store.test.ts -t "<backend>.*RFC-0045 step 2"for better-sqlite3, D1 (Miniflare) and Durable Object (Miniflare).test/store.test.ts -t better-sqlite3: 204 passed.references-store22,list-values51,list-values-rows7,list-values-sql7,list-values-query18,sync10,sync-plan9,sync-restore5,multi-site6,references-core22,fields-resolve16.list-values-jobandscheduler(11).tool-schemas6.pnpm size:checkpasses; biome reports no errors.Not run locally: the D1 and Durable Object variants of the rest of store.test.ts (left to CI), and the full suites of the other packages.
🤖 Generated with Claude Code